function PCMPlayer(option) {
  this.encodings = {
    '8bitInt': 128,
    '16bitInt': 32768,
    '32bitInt': 2147483648,
    '32bitFloat': 1
  };
  if (option) this.init(option);
}

PCMPlayer.prototype.init = function (option) {
  let defaults = {
    encoding: '16bitInt',
    channels: 1,
    sampleRate: 8000,
    flushingTime: 1000
  };
  this.option = Object.assign({}, defaults, option);
  delete (defaults);
  this.samples = new Float32Array();
  this.flush = this.flush.bind(this);
  this.interval = setInterval(this.flush, this.option.flushingTime);
  this.maxValue = this.encodings[this.option.encoding];
  if (!this.maxValue) this.maxValue = 32768;
  this.typedArray = this.getTypedArray();
  return this.createContext();
};


PCMPlayer.prototype.getTypedArray = function () {
  let typedArrays = {
    '8bitInt': Int8Array,
    '16bitInt': Int16Array,
    '32bitInt': Int32Array,
    '32bitFloat': Float32Array
  };
  return typedArrays[this.option.encoding] ? typedArrays[this.option.encoding] : typedArrays['16bitInt'];
};

PCMPlayer.prototype.createContext = function () {
  // this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  if (typeof GlobalAudioCtx == "undefined") {
    GlobalAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
    GlobalAudioCtx.resume();
    GlobalAudioCtx.suspend();
  }

  this.audioCtx = GlobalAudioCtx;
  this.gainNode = this.audioCtx.createGain();
  this.gainNode.gain.value = 1;
  this.gainNode.connect(this.audioCtx.destination);
  this.startTime = this.audioCtx.currentTime;
  // this.audioCtx.onstatechange = function () {
  //   console.log(GlobalAudioCtx.currentTime);
  // }
  return this.audioCtx;
};

PCMPlayer.prototype.isTypedArray = function (data) {
  return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer);
};

PCMPlayer.prototype.feed = function (p) {
  if (!this.isTypedArray(p)) return;
  let data = this.getFormatedValue(p);
  let tmp = new Float32Array(this.samples.length + data.length);
  tmp.set(this.samples, 0);
  tmp.set(data, this.samples.length);
  this.samples = tmp;
  delete (tmp);
  delete (data);
  delete (p);

};

PCMPlayer.prototype.getFormatedValue = function (p) {
  let data = new this.typedArray(p.buffer),
    float32 = new Float32Array(data.length),
    i;

  for (i = 0; i < data.length; i++) {
    float32[i] = data[i] / this.maxValue;
  }
  delete (p);
  delete (data);
  delete (i);
  return float32;
};

PCMPlayer.prototype.volume = function (volume) { // 调节声音
  if (typeof volume == "undefined") {
    return this.gainNode.gain.value;
  }
  this.gainNode.gain.value = volume;
  return this.gainNode.gain.value;
};

PCMPlayer.prototype.destroy = function () {
  if (this.interval) {
    clearInterval(this.interval);
  }
  this.samples = null;
  this.audioCtx.close();
  GlobalAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
  GlobalAudioCtx.resume();
  GlobalAudioCtx.suspend();
  this.audioCtx = null;
};

PCMPlayer.prototype.flush = function () {
  if (!this.samples.length) return;
  let bufferSource = this.audioCtx.createBufferSource(),
    length = this.samples.length / this.option.channels,
    audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate),
    audioData,
    channel,
    offset,
    i,
    decrement;

  for (channel = 0; channel < this.option.channels; channel++) {
    audioData = audioBuffer.getChannelData(channel);
    offset = channel;
    decrement = 50;
    for (i = 0; i < length; i++) {
      audioData[i] = this.samples[offset];
      // fadein
      if (i < 50) {
        audioData[i] = (audioData[i] * i) / 50;
      }
      // fadeout
      if (i >= (length - 51)) {
        audioData[i] = (audioData[i] * decrement--) / 50;
      }
      offset += this.option.channels;
    }
  }

  if (this.startTime < this.audioCtx.currentTime) {
    this.startTime = this.audioCtx.currentTime;
  }
  //console.log('start vs current '+this.startTime+' vs '+this.audioCtx.currentTime+' duration: '+audioBuffer.duration);
  bufferSource.buffer = audioBuffer;
  bufferSource.connect(this.gainNode);
  bufferSource.start(this.startTime);
  this.startTime += audioBuffer.duration;
  // this.startTime += parseInt(audioBuffer.duration * 100) / 100;
  delete (this.samples);
  this.samples = new Float32Array();
  delete (bufferSource);
  delete (length);
  delete (audioBuffer);
  delete (audioData);
  delete (channel);
  delete (offset);
  delete (i);
  delete (decrement);

};

PCMPlayer.prototype.getTimestamp = function () {
  if (this.audioCtx) {
    return this.audioCtx.currentTime;
  } else {
    return 0;
  }
};

PCMPlayer.prototype.play = function (pd) {
  if (!this.isTypedArray(pd)) {
    return;
  }
  let data = this.getFormatedValue(pd);
  delete (pd);
  if (!data.length) {
    console.log(3);
    return;
  }
  // console.log(this.audioCtx);
  let bufferSource = this.audioCtx.createBufferSource(),
    length = data.length / this.option.channels,
    audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate),
    audioData,
    channel,
    offset,
    i,
    decrement;

  for (channel = 0; channel < this.option.channels; channel++) {
    audioData = audioBuffer.getChannelData(channel);
    offset = channel;
    decrement = 50;
    for (i = 0; i < length; i++) {
      audioData[i] = data[offset];
      /* fadein */
      if (i < 50) {
        audioData[i] = (audioData[i] * i) / 50;
      }
      /* fadeout*/
      if (i >= (length - 51)) {
        audioData[i] = (audioData[i] * decrement--) / 50;
      }
      offset += this.option.channels;
    }
  }

  if (this.startTime < this.audioCtx.currentTime) {
    this.startTime = this.audioCtx.currentTime;
  }
  let ret = audioBuffer.duration;//parseInt(audioBuffer.duration * 100) / 100;
  ret = parseInt(audioBuffer.duration * this.audioCtx.sampleRate - 1) / this.audioCtx.sampleRate;
  bufferSource.buffer = audioBuffer;
  bufferSource.connect(this.gainNode);
  bufferSource.start(this.startTime);
  this.startTime += ret;

  delete (bufferSource);
  delete (length);
  delete (audioBuffer);
  delete (audioData);
  delete (channel);
  delete (offset);
  delete (i);
  delete (decrement);
  delete (pd);
  delete (callback);
  delete (data);
  // console.log(ret, this.audioCtx.sampleRate);
  return ret;
};

PCMPlayer.prototype.pause = function () {
  if (this.audioCtx.state === 'running') {
    this.audioCtx.suspend();
    return true;
  }
};

PCMPlayer.prototype.resume = function () {
  if (this.audioCtx.state === 'suspended') {
    this.audioCtx.resume();
    return true;
  }
};


